home *** CD-ROM | disk | FTP | other *** search
- /* errmgr.cpp - error manager */
-
- /* see errmgr.hpp for details of the definition of this module. */
-
- #include <stdio.h> /* various and sundry, esp. stderr */
- #include <ctype.h> /* toupper() */
- #include <stdlib.h> /* exit() */
- #include <stdarg.h> /* varargs */
- #include <string.h>
- #include "complain.hpp" /* complaint dictionary interface */
- #include "utils.hpp"
- #include "c_failur.hpp" /* counting_failure_handler */
- #include "errmgr.hpp" /* us */
-
- /* an element in the list of complaint dictionaries: */
-
- class error_dict_list { /* LIFO list (not stack; no pop) */
- public:
- error_dict_list(error_dict_list *curr,complaint_dict *new_dict);
- error_dict_list *prev;
- complaint_dict *dict;
- }; /* end of class error_dict_list */
-
-
- error_mgr err_mgr; /* declaration, not definition */
-
- /* is_set_up is set when err_mgr is initialized. no other error manager can */
- /* be active. */
-
- int error_mgr::is_set_up = 0;
-
- /* asserts_off is 0 when assertions are to be checked. it is set */
- /* whenever set_assert_flag() is called with a 0 parameter. */
-
- /* NDEBUG controls only the default value of the assertions flag, as */
- /* opposed to whether assertions are run or not. if NDEBUG is defined */
- /* then assertions must be requested specifically by the application. */
-
- #ifndef NDEBUG
- int error_mgr::asserts_off = 0;
- #else
- int error_mgr::asserts_off = 1;
- #endif
-
- /* all of these class static variables are set to 0 because we cannot */
- /* ensure that errors will not be reported in some constructor before */
- /* err_mgr is constructed. only assignments to 0 are guaranteed to be */
- /* performed before any constructor is called. */
-
- failure_handler *error_mgr::curr_handler = 0,
- *error_mgr::default_handler = 0;
- error_dict_list *error_mgr::error_dicts = 0;
- ptr_stack *error_mgr::handler_stack = 0;
-
- /* this demonstration implementation assumes that the buffer won't */
- /* overflow; it really should use a managed buffer that grows to fit the */
- /* message text. */
-
- static char line[512];
-
- /* compares two printf() format strings and returns 1 if they are */
- /* functionally equivalent (all format specifiers in the same order). */
-
- static int proper_format(const char *pgm_text,const char *user_text);
-
- /* finds the next printf() format specifier and returns a unique character */
- /* identifying its type. advances *fmt to point past the specifier. */
-
- static char format_specifier(const char **fmt);
-
- error_mgr::error_mgr(void) /* constructor */
- {
- /* it's possible that an error in a constructor may cause code */
- /* to be invoked before the constructor is run. as a result */
- /* everything calls setup() first. we call it here just in case */
- /* it hasn't been done yet. */
-
- setup();
-
- } /* end of error_mgr::error_mgr() */
-
- error_mgr::~error_mgr(void) /* destructor */
- {
- /* no point in creating more "memory leaks" to sign off on. */
-
- delete default_handler;
- delete handler_stack;
-
- } /* end of error_mgr::~error_mgr() */
-
- void error_mgr::setup(void)
- {
- /* setup: create a default handler and an empty dictionary list. */
- /* also allocate a pointer stack for failure_handlers. make sure */
- /* that no other error_mgr is ever defined. */
-
- /* this routine gets called by the "working" routines to ensure that */
- /* everything is functional should an error message be called by a */
- /* constructor before this module would be initialized. we only know */
- /* that initializations to 0 are guaranteed. */
-
- if (this != &err_mgr) /* just in case... */
- err_mgr.fail("Duplicate error_mgr was defined.\n");
- if (is_set_up)
- return;
- is_set_up = 1;
- curr_handler = default_handler = new failure_handler;
- handler_stack = new ptr_stack;
-
- } /* end of error_mgr::setup() */
-
- int error_mgr::define_dictionary(const char *filename)
- {
- /* error dictionary management - add a new file to the list of key */
- /* lookups. returns 0 if any errors are detected; the dictionary */
- /* is invalid and will not be referenced. */
-
- counting_failure_handler *our_handler;
- complaint_dict *new_dict;
-
- /* in reality, the dictionary may in fact have some valid definitions */
- /* in it, but I've drawn a hard line here and declared that it will */
- /* not be used. */
-
- /* note the use of the counting handler to tally errors in the file. */
- /* this gets us around the "no constructor return value" problem. */
-
- setup();
- our_handler = new counting_failure_handler; /* inserts automatically */
- new_dict = new complaint_dict(filename);
- if (our_handler->errors_logged() || our_handler->warns_logged()) {
- delete new_dict;
- warn("$bad_error_dict: dictionary file \"%s\" has "
- "errors - will not be used\n",filename);
- delete our_handler; /* removes self automatically */
- return 0;
- }
-
- /* everything looks good - install the dictionary in the chain. */
-
- error_dicts = new error_dict_list(error_dicts,new_dict);
- delete our_handler;
- return 1;
-
- } /* end of error_mgr::define_dictionary() */
-
- failure_handler *error_mgr::define_handler(failure_handler *new_handler)
- {
- /* install the argument as the new error handler, pushing the previous */
- /* handler onto a stack. a GUI, for example, would supply a window-based */
- /* handler upon startup and return to the previous handler on exit (in */
- /* case there are any errors after the GUI has shut down). */
-
- failure_handler *old_handler;
-
- setup();
- handler_stack->push(curr_handler);
- old_handler = curr_handler;
- if (new_handler == 0) /* must be bomb-proof */
- curr_handler = default_handler;
- else
- curr_handler = new_handler;
- return old_handler;
-
- } /* end of error_mgr::define_handler() */
-
- failure_handler *error_mgr::restore_handler(void)
- {
- /* return to the previous error handler, falling back to the default */
- /* handler should the user go too far in popping handlers. */
-
- failure_handler *old_handler;
-
- setup();
- old_handler = curr_handler;
- curr_handler = (failure_handler *) (handler_stack->pop());
- if (curr_handler == 0)
- curr_handler = default_handler;
- return old_handler;
-
- } /* end of error_mgr::restore_handler() */
-
- const char *error_mgr::message(const char *fmt,char *msg_line,
- int linelen)
- {
- /* the format of an error message being passed to fail(), vfail(), etc. */
- /* [ '$' <key> ':' ] <default message>. if the first character of the */
- /* message is '$' then split off the key and look it up. if it's found */
- /* and if it has the proper format, return the text defined for the */
- /* key instead. otherwise, prepend a warning to the default message */
- /* and print the default. thus, we get a response out even if no */
- /* dictionaries are defined or if there is an error in the dictionary */
- /* containing the key. */
-
- /* interface: this function can also be used to return a string value */
- /* for later insertion into printed text. */
-
- /* the message text is written into msg_line, which is returned so that */
- /* a call to this function can be used in printf(). */
-
- const char *new_fmt,*key,*keystart;
- char *key_alloc,*buf;
- int keylen;
-
- /* since fail(), vfail(), error(), verror(), warn(), vwarn(), post(), */
- /* and vpost() all call message() first, this routine calls setup() */
- /* for them. */
-
- setup();
- ASSERT(fmt != msg_line); /* can't write into fmt! */
- msg_line[0] = '\0';
- if (*fmt != '$') { /* not a keyed message? */
- strncpy(msg_line,fmt,linelen);
- msg_line[linelen - 1] = '\0'; /* in case strncpy() didn't */
- return msg_line;
- }
-
- /* extract the key, which must be alphanumeric (just like in an error */
- /* dictionary). note that fmt is advanced past the '$'; we leave it */
- /* there in case the replacement text is ill-formed so that the user */
- /* will know which text to fix. */
-
- key = ++fmt; /* skip '$' */
- key += skipblanks(key); /* skip leading blanks */
- keylen = skip_ident(key); /* fetch key name */
- new_fmt = key + keylen; /* skip key name */
- new_fmt += skipblanks(key); /* skip to ':' */
-
- /* if the format string is malformed, prepend a nasty message and pass */
- /* it on. here we assume it will fit; this should be writing into a */
- /* managed buffer. */
-
- /* note that no error within the error system (errmgr.cpp or */
- /* complain.cpp) is replaceable: a recursive call, with the potential */
- /* for other errors, would be dangerous. */
-
- if (keylen == 0 || *new_fmt != ':') { /* malformed format string! */
- sprintf(msg_line,"Keyed error message format string is malformed\n%s",
- fmt);
- }
- else {
- key_alloc = newstring(key,keylen);
- ++new_fmt; /* skip ':' */
- new_fmt += skipblanks(new_fmt);
- if (find_replacement(key_alloc,msg_line,linelen)) {
- if (!proper_format(new_fmt,msg_line)) {
-
- /* we leave the dictionary text at the front of the */
- /* message. */
-
- buf = msg_line + strlen(msg_line);
- sprintf(buf,"Replacement text for keyed error "
- "message is malformed\n%s",fmt);
- }
- } /* end of if(replacement text found) */
-
- /* the text wasn't found - use the program version. */
-
- else {
- strncpy(msg_line,new_fmt,linelen);
- msg_line[linelen - 1] = '\0'; /* in case strncpy() didn't */
- }
- free(key_alloc);
- } /* end of else(!malformed) */
-
- return msg_line;
-
- } /* end of error_mgr::message() */
-
- int error_mgr::find_replacement(const char *key,char *msg_line,int linelen)
- {
- /* if there is a replacement for the message indexed by key, then */
- /* put it into the buffer. return 1 if found, 0 if not. */
-
- error_dict_list *curr_dict;
-
- for (curr_dict = error_dicts; curr_dict != 0;
- curr_dict = curr_dict->prev)
- if (curr_dict->dict->key_defined(key))
- break;
- return curr_dict != 0 &&
- curr_dict->dict->complaint_text(key,msg_line,linelen);
-
- } /* end of error_mgr::find_replacement() */
-
- static int proper_format(const char *pgm_text,const char *user_text)
- {
- /* return 1 if the printf() format specifiers in user_text are the */
- /* same (and in the same order) as the ones in pgm_text. */
-
- char pgm_fmt,user_fmt;
-
- do {
-
- /* get the next format character (i.e. the 'd' from "%d") from each */
- /* string and compare them. we use a canonical form. */
-
- pgm_fmt = format_specifier(&pgm_text);
- user_fmt = format_specifier(&user_text);
- if (pgm_fmt != user_fmt)
- return 0;
- } while (*pgm_text || *user_text); /* can't have extras anywhere! */
-
- return 1;
-
- } /* end of proper_format() */
-
- static char format_specifier(const char **fmt)
- {
- /* skip to the next '%' specifier (if any) and return a unique letter */
- /* indicating what type it is. some specifiers get mapped to a */
- /* canonical form: "ld" is mapped to 'l'; 'e', 'f', and 'g' are mapped */
- /* to 'f', etc. */
-
- /* returns '\0' when **fmt runs out. advances *fmt past the specifier. */
-
- const char *s = *fmt; /* easier to use *s than **fmt */
- char c;
-
- /* first, look for a format specifier. */
-
- for (;;) { /* exit from within */
- while (*s && *s != '%') /* skip to '%' */
- ++s;
- if (*s == '\0') /* off end? */
- break;
- ++s; /* skip '%' */
- if (*s == '%') /* "%%" prints '%' */
- ++s; /* not a format specifier */
- else
- break; /* anything else - assume specifier */
- } /* end of for(ever) */
-
- if (*s == '\0') { /* fell off end? */
- *fmt = s; /* update caller's variable */
- return '\0';
- }
-
- /* skip all of the auxiliary junk until we get to the specifying */
- /* letter. we don't test the validity of the junk; to do so would */
- /* almost duplicate the functionality of printf(). */
-
- while (*s && !isspace(*s) && !isalpha(*s))
- ++s;
- if (!isalpha(*s)) {
- *fmt = s; /* update caller's variable */
- return '\0'; /* bad format specifier */
- }
-
- /* if we find an 'l' we assume an integer specification follows */
- /* and return an 'l'. what's important is that a long integer */
- /* is to be pulled off the stack when printf() gets here. */
-
- if (*s == 'l' && isalpha(*(s + 1))) { /* long (integer assumed) */
- *fmt = s + 2; /* skip 'l' and specifier */
- return 'l'; /* something done with a long */
- }
-
- /* the format specifier is probably OK (though we don't examine it */
- /* more closely). map it into something canonical and return it. */
-
- c = tolower(*s);
- *fmt = s + 1; /* update caller's variable */
- switch(c) {
- case 'e': /* floating point specifiers */
- case 'f': /* a double is pulled off stack */
- case 'g':
- return 'e';
- case 'o': /* integer specifiers */
- case 'u':
- case 'x':
- case 'd':
- case 'i':
- case 'b':
- return 'd';
- default: /* all others - map into themselves */
- return c;
- } /* end of switch(c) */
-
- /* NOTREACHED */
-
- } /* end of format_specifier() */
-
- void error_mgr::fail(const char *fmt,...)
- {
- /* something horrible has happened. print the message and exit. note */
- /* that the handler's fail() routine should exit, but in case it */
- /* doesn't we enforce the decision. a failure is taken seriously. */
- /* replace the format text if it's keyed. */
-
- va_list ap;
-
- va_start(ap,fmt);
- vfail(fmt,ap); /* pass it on - won't return */
-
- /* NOTREACHED */
-
- va_end(ap);
-
- } /* end of error_mgr::fail() */
-
- void error_mgr::vfail(const char *fmt,va_list ap)
- {
- /* fail() with a va_list already built. see its description above. */
-
- fmt = message(fmt,line,sizeof(line)); /* get replacement text (if any) */
- curr_handler->fail(fmt,ap);
- exit(EXIT_FAILURE); /* just in case handler didn't! */
-
- } /* end of error_mgr::vfail() */
-
- void error_mgr::error(const char *fmt,...)
- {
- /* print the message and ask for permission to continue. this is all */
- /* the responsibility of the handler, of course. replace the format */
- /* text if it's keyed. */
-
- va_list ap;
-
- va_start(ap,fmt);
- verror(fmt,ap); /* pass it on */
- va_end(ap);
-
- } /* end of error_mgr::error() */
-
- void error_mgr::verror(const char *fmt,va_list ap)
- {
- /* error() with a va_list already built. see its description above. */
-
- fmt = message(fmt,line,sizeof(line)); /* get replacement text (if any) */
- curr_handler->error(fmt,ap);
-
- } /* end of error_mgr::verror() */
-
- void error_mgr::warn(const char *fmt,...)
- {
- /* just have the handler print the message. replace the format text if */
- /* it's keyed. */
-
- va_list ap;
-
- va_start(ap,fmt);
- vwarn(fmt,ap); /* pass it on */
- va_end(ap);
-
- } /* end of error_mgr::warn() */
-
- void error_mgr::vwarn(const char *fmt,va_list ap)
- {
- /* warn() with a va_list already built. see its description above. */
-
- fmt = message(fmt,line,sizeof(line)); /* get replacement text (if any) */
- curr_handler->warn(fmt,ap);
-
- } /* end of error_mgr::vwarn() */
-
- void error_mgr::post(const char *fmt,...)
- {
- /* just have the handler print the message. replace the format text if */
- /* it's keyed. */
-
- va_list ap;
-
- va_start(ap,fmt);
- vpost(fmt,ap); /* pass it on */
- va_end(ap);
-
- } /* end of error_mgr::post() */
-
- void error_mgr::vpost(const char *fmt,va_list ap)
- {
- /* post() with a va_list already built. see its description above. */
-
- fmt = message(fmt,line,sizeof(line)); /* get replacement text (if any) */
- curr_handler->post(fmt,ap);
-
- } /* end of error_mgr::vpost() */
-
- int error_mgr::set_assert_flag(int asserts_on)
- {
- /* enable or disable assertions at run time. returns the previous */
- /* state of the assertion flag. note that the internal flag has the */
- /* opposite state since we want to use it to short-circuit assertion */
- /* evaluation. */
-
- int retval = asserts_off;
-
- asserts_off = !asserts_on;
- return !retval;
-
- } /* end of error_mgr::set_assert_flag() */
-
- int error_mgr::assert_failed(const char *exp,const char *fname,
- unsigned linenum)
- {
- /* an assertion has failed. print the file name, the line number, */
- /* and the expression. error() allows the user to continue; for */
- /* now I think this is a good idea. I may later change this to call */
- /* fail() instead. */
-
- error("Assertion failed - file %s, line %u:\n%s\n",fname,linenum,exp);
- return 1; /* an arbitrary value (needed for */
- /* the macro definition) */
- } /* end of error_mgr::assert_failed() */
-
-
- /* this is the default handler. its routines just send the message to */
- /* stderr. of course, to override these, inherit from the failure_handler */
- /* class and supply new ones. remember to assign the new handler using */
- /* define_handler()! */
-
- static int ok_to_continue(void);
-
- void failure_handler::fail(const char *fmt,va_list ap)
- {
- /* a fatal error with arguments to be formatted. */
-
- fputs("FATAL: ",stderr);
- vfprintf(stderr,fmt,ap);
- exit(EXIT_FAILURE); /* that was all she wrote... */
-
- } /* end of failure_handler::fail() */
-
- void failure_handler::error(const char *fmt,va_list ap)
- {
- /* a serious error with arguments to be formatted. ask for permission */
- /* to continue. */
-
- fputs("ERROR: ",stderr);
- vfprintf(stderr,fmt,ap);
- if (!ok_to_continue())
- exit(EXIT_FAILURE);
-
- } /* end of failure_handler::error() */
-
- void failure_handler::warn(const char *fmt,va_list ap)
- {
- /* a warning with arguments to be formatted */
-
- fputs("WARNING: ",stderr);
- vfprintf(stderr,fmt,ap);
-
- } /* end of failure_handler::warn() */
-
- void failure_handler::post(const char *fmt,va_list ap)
- {
- /* a message with arguments to be formatted. goes to stdout by */
- /* default, unlike the other routines. */
-
- vfprintf(stdout,fmt,ap); /* no prefix text */
-
- } /* end of failure_handler::post() */
-
- static int ok_to_continue(void)
- {
- /* request permission to continue. barring knowledge that I can read */
- /* from stderr as well as write to it, I read from stdin. cave canem. */
-
- /* real cheap hack: I only look at the first character entered. if */
- /* it's a 'y' or 'Y', then it's OK. otherwise we bomb. don't forget, */
- /* this is the default behavior. if you want something better, then */
- /* define your own class! */
-
- char buf[80];
-
- fputs("OK to continue [no]? ",stderr);
- fgets(buf,sizeof(buf),stdin);
- return toupper(buf[0]) == 'Y';
-
- } /* end of ok_to_continue() */
-
- /* the constructor for the dictionary list - just links in the previous */
- /* head of the list. use: */
- /* error_dicts = new error_dict_list(error_dicts,new_dict); */
-
- error_dict_list::error_dict_list(error_dict_list *curr,
- complaint_dict *new_dict)
- {
- prev = curr; /* link ourselves in */
- dict = new_dict;
-
- } /* end of error_dict_list::error_dict_list() */